package io.github.bigbird0101.pipeline;

import java.util.*;

public class DefaultEventPipeline implements Pipeline {
    private volatile DefaultEventHandlerContext head;
    private volatile DefaultEventHandlerContext tail;
    private final Map<String, DefaultEventHandlerContext> name2Ctx = new HashMap<>();

    @Override
    public synchronized List<String> getNames() {
        List<String> list = new ArrayList<>();
        if (name2Ctx.isEmpty()) {
            return list;
        }
        DefaultEventHandlerContext next = head;
        while (next != null) {
            list.add(next.getName());
            next = next.next;
        }
        return list;
    }

    @Override
    public synchronized Optional<Handler> get(String name) {
        DefaultEventHandlerContext ctx = name2Ctx.get(name);
        if (ctx == null) {
            return Optional.empty();
        } else {
            return Optional.of(ctx.getHandler());
        }
    }

    @Override
    public synchronized Optional<Handler> getFirst() {
        return Optional.ofNullable(head).map(DefaultEventHandlerContext::getHandler);
    }

    @Override
    public synchronized Optional<Handler> getNext(Handler handler) {
        if (null == handler) {
            throw new NullPointerException("handler is empty");
        }
        DefaultEventHandlerContext next = head;
        while (next != null) {
            Handler handlerInner = next.getHandler();
            if (handlerInner.equals(handler)) {
                return Optional.ofNullable(next.next).map(DefaultEventHandlerContext::getHandler);
            }
            next = next.next;
        }
        return Optional.empty();
    }

    @Override
    public synchronized Optional<Handler> getLast() {
        return Optional.ofNullable(tail).map(DefaultEventHandlerContext::getHandler);
    }

    @Override
    public synchronized Optional<HandlerContext> getContext(Handler handler) {
        if (null == handler) {
            throw new NullPointerException("handler is empty");
        }
        DefaultEventHandlerContext next = head;
        while (next != null) {
            Handler handlerInner = next.getHandler();
            if (handlerInner.getClass().isAssignableFrom(handler.getClass())) {
                return Optional.of(next);
            }
            next = next.next;
        }
        return Optional.empty();
    }

    @Override
    public synchronized Optional<HandlerContext> getContext(String name) {
        if (null == name) {
            throw new NullPointerException("name is empty");
        }
        return Optional.ofNullable(name2Ctx.get(name));
    }

    @Override
    public synchronized Optional<HandlerContext> getContext(Class<? extends Handler> handlerType) {
        if (null == handlerType) {
            throw new NullPointerException("handlerType is empty");
        }
        DefaultEventHandlerContext next = head;
        while (next != null) {
            Handler handlerInner = next.getHandler();
            if (handlerType.isAssignableFrom(handlerInner.getClass())) {
                return Optional.of(next);
            }
            next = next.next;
        }
        return Optional.empty();
    }

    @Override
    @SuppressWarnings("unchecked")
    public synchronized <T extends Handler> Optional<T> get(Class<T> handlerType) {
        if (null == handlerType) {
            throw new NullPointerException("handlerType is empty");
        }
        DefaultEventHandlerContext next = head;
        while (next != null) {
            T handlerInner = (T) next.getHandler();
            if (handlerInner.getClass().isAssignableFrom(handlerType)) {
                return Optional.of(handlerInner);
            }
            next = next.next;
        }
        return Optional.empty();
    }

    @Override
    public synchronized void addFirst(String name, Handler handler) {
        checkNulAndEventHandler(handler);
        if (name2Ctx.isEmpty()) {
            DefaultEventHandlerContext newHead = new DefaultEventHandlerContext(name, null, null, (EventHandler) handler);
            head = tail = newHead;
            name2Ctx.put(newHead.getName(), newHead);
        } else {
            checkDuplicateName(name);
            DefaultEventHandlerContext oldHead = head;
            DefaultEventHandlerContext newHead = new DefaultEventHandlerContext(name, null, oldHead, (EventHandler) handler);
            oldHead.pre = newHead;
            head = newHead;
            name2Ctx.put(newHead.getName(), newHead);
        }
    }

    private void checkDuplicateName(String name) {
        if (name2Ctx.containsKey(name)) {
            throw new IllegalArgumentException(String.format("%s Duplicate handler name.", name));
        }
    }

    @Override
    public synchronized void addLast(String name, Handler handler) {
        checkNulAndEventHandler(handler);
        if (name2Ctx.isEmpty()) {
            DefaultEventHandlerContext newTail = new DefaultEventHandlerContext(name, null, null, (EventHandler) handler);
            head = tail = newTail;
            name2Ctx.put(newTail.getName(), newTail);
        } else {
            checkDuplicateName(name);
            DefaultEventHandlerContext oldTail = tail;
            DefaultEventHandlerContext newTail = new DefaultEventHandlerContext(name, oldTail, null, (EventHandler) handler);
            oldTail.next = newTail;
            tail = newTail;
            name2Ctx.put(newTail.getName(), newTail);
        }
    }

    @Override
    public synchronized void addBefore(String baseName, String name, Handler handler) {
        checkBaseName(baseName);
        checkNulAndEventHandler(handler);
        DefaultEventHandlerContext context = name2Ctx.get(baseName);
        if (context == head) {
            addFirst(name, handler);
        } else {
            DefaultEventHandlerContext pre = context.pre;
            DefaultEventHandlerContext current = new DefaultEventHandlerContext(name, pre, context, (EventHandler) handler);
            pre.next = current;
            context.pre = current;
            name2Ctx.put(current.getName(), current);
        }
    }

    private void checkNulAndEventHandler(Handler handler) {
        if (null == handler) {
            throw new NullPointerException("handler is null");
        }
        if (!(handler instanceof EventHandler)) {
            throw new IllegalArgumentException("handler is not event handler");
        }
    }

    @Override
    public synchronized void addAfter(String baseName, String name, Handler handler) {
        checkBaseName(baseName);
        checkNulAndEventHandler(handler);
        DefaultEventHandlerContext context = name2Ctx.get(baseName);
        if (context == tail) {
            addLast(name, handler);
        } else {
            DefaultEventHandlerContext next = context.next;
            DefaultEventHandlerContext current = new DefaultEventHandlerContext(name, context, next, (EventHandler) handler);
            next.pre = current;
            context.next = current;
            name2Ctx.put(current.getName(), current);
        }
    }

    private void checkBaseName(String baseName) {
        if (!name2Ctx.containsKey(baseName)) {
            throw new IllegalArgumentException(String.format("name :%s is not exists ", baseName));
        }
    }

    @Override
    public synchronized Handler remove(String name) {
        checkBaseName(name);
        DefaultEventHandlerContext context = name2Ctx.get(name);
        if (head == tail) {
            head = tail = null;
            name2Ctx.clear();
            return context.getHandler();
        } else if (context == head) {
            return removeFirst();
        } else if (context == tail) {
            return removeLast();
        } else {
            DefaultEventHandlerContext pre = context.pre;
            DefaultEventHandlerContext next = context.next;
            next.pre = pre;
            pre.next = next;
            name2Ctx.remove(name);
            return context.getHandler();
        }
    }

    @Override
    public synchronized Handler removeFirst() {
        if (name2Ctx.isEmpty()) {
            throw new NoSuchElementException();
        }
        DefaultEventHandlerContext remove = head;
        if (remove.next == null) {
            Handler handler = remove.getHandler();
            name2Ctx.remove(remove.name);
            head = tail = null;
            return handler;
        } else {
            DefaultEventHandlerContext next = remove.next;
            name2Ctx.remove(remove.name);
            head = next;
            next.pre = null;
            return remove.getHandler();
        }
    }

    @Override
    public synchronized Handler removeLast() {
        if (name2Ctx.isEmpty()) {
            throw new NoSuchElementException();
        }
        DefaultEventHandlerContext remove = tail;
        if (tail.pre == null) {
            Handler handler = remove.getHandler();
            name2Ctx.remove(remove.name);
            head = tail = null;
            return handler;
        } else {
            DefaultEventHandlerContext pre = remove.pre;
            name2Ctx.remove(remove.name);
            tail = pre;
            pre.next = null;
            return remove.getHandler();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public synchronized <T extends Handler> T remove(Class<T> handlerType) {
        return (T) getContext(handlerType)
                .map(HandlerContext::getName)
                .map(this::remove)
                .orElse(null);
    }

    @Override
    public synchronized void replace(Handler oldHandler, String newName, Handler newHandler) {
        HandlerContext handlerContext = getContext(oldHandler).orElseThrow(NoSuchElementException::new);
        if (!(handlerContext instanceof DefaultEventHandlerContext)) {
            throw new IllegalArgumentException("handlerContext is not DefaultEventHandlerContext");
        }
        checkNulAndEventHandler(newHandler);
        replace((DefaultEventHandlerContext) handlerContext,
                new DefaultEventHandlerContext(newName, null, null, (EventHandler) newHandler));
    }

    @Override
    public synchronized Handler replace(String oldName, String newName, Handler newHandler) {
        HandlerContext handlerContext = getContext(oldName).orElseThrow(NoSuchElementException::new);
        if (!(handlerContext instanceof DefaultEventHandlerContext)) {
            throw new IllegalArgumentException("handlerContext is not DefaultEventHandlerContext");
        }
        checkNulAndEventHandler(newHandler);
        return replace((DefaultEventHandlerContext) handlerContext,
                new DefaultEventHandlerContext(newName, null, null, (EventHandler) newHandler));
    }

    @Override
    @SuppressWarnings("unchecked")
    public synchronized <T extends Handler> T replace(Class<T> handlerType, String newName, Handler newHandler) {
        HandlerContext handlerContext = getContext(handlerType)
                .orElseThrow(NoSuchElementException::new);
        if (!(handlerContext instanceof DefaultEventHandlerContext)) {
            throw new IllegalArgumentException("handlerContext is not DefaultEventHandlerContext");
        }
        checkNulAndEventHandler(newHandler);
        return (T) replace((DefaultEventHandlerContext) handlerContext,
                new DefaultEventHandlerContext(newName, null, null, (EventHandler) newHandler));
    }

    private Handler replace(DefaultEventHandlerContext oldHandlerContext, DefaultEventHandlerContext newHandlerContext) {
        //当没有值或者只有一个值的时候
        if (oldHandlerContext == head) {
            Handler handler = removeFirst();
            addFirst(newHandlerContext.name, newHandlerContext.eventHandler);
            return handler;
        } else if (oldHandlerContext == tail) {
            Handler handler = removeLast();
            addLast(newHandlerContext.name, newHandlerContext.eventHandler);
            return handler;
        } else {
            String name = oldHandlerContext.getName();
            String newName = newHandlerContext.getName();
            boolean isSameName = name.equals(newName);
            if(!isSameName){
                checkDuplicateName(newName);
            }
            DefaultEventHandlerContext pre = oldHandlerContext.pre;
            DefaultEventHandlerContext next = oldHandlerContext.next;
            newHandlerContext.pre = pre;
            pre.next = newHandlerContext;
            next.pre = newHandlerContext;
            newHandlerContext.next = next;
            if(!isSameName) {
                name2Ctx.remove(oldHandlerContext.getName());
                name2Ctx.put(newName,newHandlerContext);
            }else{
                name2Ctx.put(newName,newHandlerContext);
            }
            return oldHandlerContext.getHandler();
        }
    }

    public void sendHandlerEvent(HandlerEvent handlerEvent){
        if(head==null){
            return;
        }
        ((EventHandler)(head.getHandler())).handle(head,handlerEvent);
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(getClass().getSimpleName());
        buf.append('{');
        DefaultEventHandlerContext ctx = head;
        for (; ; ) {
            buf.append('(');
            buf.append(ctx.getName());
            buf.append(" = ");
            buf.append(ctx.getHandler().getClass().getName());
            buf.append(')');
            ctx = ctx.next;
            if (ctx == null) {
                break;
            }
            buf.append(", ");
        }
        buf.append('}');
        return buf.toString();
    }

    private class DefaultEventHandlerContext implements EventHandlerContext {

        private volatile DefaultEventHandlerContext pre;
        private volatile DefaultEventHandlerContext next;
        private final EventHandler eventHandler;

        private volatile Object attachment;

        private final String name;

        DefaultEventHandlerContext(String name, DefaultEventHandlerContext pre, DefaultEventHandlerContext next, EventHandler eventHandler) {
            if (name == null) {
                throw new NullPointerException("name");
            }
            if (eventHandler == null) {
                throw new NullPointerException("eventHandler");
            }
            this.name = name;
            this.pre = pre;
            this.next = next;
            this.eventHandler = eventHandler;
        }

        @Override
        public void sendEvent(HandlerEvent handlerEvent) {
            if(null!=next){
                ((EventHandler)next.getHandler()).handle(next,handlerEvent);
            }
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public Handler getHandler() {
            return eventHandler;
        }

        @Override
        public Pipeline getPipeline() {
            return DefaultEventPipeline.this;
        }

        @Override
        public Object getAttachment() {
            return attachment;
        }

        @Override
        public void setAttachment(Object attachment) {
            this.attachment = attachment;
        }
    }
}
